/*
*  Copyright 2019-2020 Zheng Jie
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package com.alien.admin.modules.publisher.service.impl;

import com.alien.admin.context.task.TaskQueue;
import com.alien.admin.exception.BadRequestException;
import com.alien.admin.modules.deploy.domain.DeployRec;
import com.alien.admin.modules.deploy.repository.DeployRecRepository;
import com.alien.admin.modules.deploy.service.dto.DeployRecQueryCriteria;
import com.alien.admin.modules.publisher.domain.PublishDeploy;
import com.alien.admin.modules.publisher.repository.PublishDeployRepository;
import com.alien.admin.modules.publisher.repository.PublishSheetFlowRepository;
import com.alien.admin.modules.publisher.service.PublishDeployService;
import com.alien.admin.modules.publisher.service.PublishSheetFlowService;
import com.alien.admin.modules.publisher.service.dto.PublishDeployDto;
import com.alien.admin.modules.publisher.service.dto.PublishDeployQueryCriteria;

import com.alien.admin.modules.publisher.service.mapstruct.PublishDeployMapper;
import com.alien.admin.utils.*;
import com.alien.admin.utils.enums.FlowNodeIdEnum;
import com.alien.admin.utils.enums.FlowStatusEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
* @website https://el-admin.vip
* @description 服务实现
* @author zhouseyi
* @date 2021-11-11
**/
@Service
@RequiredArgsConstructor
public class PublishDeployServiceImpl implements PublishDeployService {

    private final PublishDeployRepository publishDeployRepository;
    private final PublishDeployMapper publishDeployMapper;
    private final PublishSheetFlowService publishSheetFlowService;
    private final DeployRecRepository deployRecRepository;
    private final TaskQueue taskQueue;
    private final PublishSheetFlowRepository publishSheetFlowRepository;

    public List<PublishDeployDto> toDto(List<PublishDeploy> publishDeployList) {
        List<PublishDeployDto> publishDeployDtoList = publishDeployMapper.toDto(publishDeployList);
        publishDeployDtoList.forEach(publishDeploy -> {
            String title = publishSheetFlowRepository.queryTitleById(publishDeploy.getFlowId());
            publishDeploy.setTitle(title);
        });
        return publishDeployDtoList;
    }

    public PublishDeployDto toDto(PublishDeploy publishDeploy) {
        PublishDeployDto publishDeployDto = publishDeployMapper.toDto(publishDeploy);
        String title = publishSheetFlowRepository.queryTitleById(publishDeploy.getFlowId());
        publishDeployDto.setTitle(title);
        return publishDeployDto;
    }

    @Override
    public Map<String,Object> queryAll(PublishDeployQueryCriteria criteria, Pageable pageable){
        Page<PublishDeploy> page = publishDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
        return PageUtil.toPage(page.map(this::toDto));
    }

    @Override
    public List<PublishDeployDto> queryAll(PublishDeployQueryCriteria criteria){
        return toDto(publishDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
    }

    @Override
    @Transactional
    public PublishDeployDto findById(Integer id) {
        PublishDeploy publishDeploy = publishDeployRepository.findById(id).orElseGet(PublishDeploy::new);
        ValidationUtil.isNull(publishDeploy.getId(),"PublishDeploy","id",id);
        return publishDeployMapper.toDto(publishDeploy);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublishDeployDto create(PublishDeploy resources) {
        return publishDeployMapper.toDto(publishDeployRepository.save(resources));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(PublishDeploy resources) {
        PublishDeploy publishDeploy = publishDeployRepository.findById(resources.getId().intValue()).orElseGet(PublishDeploy::new);
        ValidationUtil.isNull( publishDeploy.getId(),"PublishDeploy","id",resources.getId());
        publishDeploy.copy(resources);
        publishDeployRepository.save(publishDeploy);
    }

    @Override
    public void deleteAll(Integer[] ids) {
        for (Integer id : ids) {
            publishDeployRepository.deleteById(id);
        }
    }

    @Override
    public void download(List<PublishDeployDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (PublishDeployDto publishDeploy : all) {
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("流程id", publishDeploy.getFlowId());
            map.put("环境", publishDeploy.getEnv());
            map.put("部署时间", publishDeploy.getPublishTime());
            map.put("阶段：0:合并分支 1:服务部署 2:分支回滚 3.服务回滚 4.结束", publishDeploy.getStage());
            map.put("状态：0:进行中 1:回滚 2:成功 3.失败", publishDeploy.getStatus());
            map.put("部署人id", publishDeploy.getOperatorId());
            map.put("部署人姓名", publishDeploy.getOperatorName());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    private boolean deploySuccess(DeployRec deployRec) {
        Integer stage = deployRec.getStage();
        Integer deployStatus = deployRec.getDeployStatus();
        return DeployConstant.DEPLOY_STATUS_SUCCESS.equals(deployStatus) && DeployConstant.DEPLOY_STAGE_HEALTH_CHECK.equals(stage);
    }

    List<DeployRec> queryDeployRecList(Long publishDeployId) {
        DeployRecQueryCriteria criteria = new DeployRecQueryCriteria();
        criteria.setPublishDeployId(publishDeployId);

        List<DeployRec> flowDeployRecList = deployRecRepository.findAll((root, criteriaQuery, criteriaBuilder)
                -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));

        return flowDeployRecList;
    }

    private void processFailed(PublishDeployDto publishDeployDto, List<DeployRec> flowDeployRecList) {
        // 通审批流程内的其他部署流程，加入取消名单
        List<Long> cancelRecIdList = flowDeployRecList.stream()
                .map(rec -> rec.getId()).collect(Collectors.toList());
        taskQueue.appendCancelTask(cancelRecIdList);

        // 代码分支回滚,分支跟审批流程关联，因此放这边回滚
        publishSheetFlowService.rollbackBranchByFlow(publishDeployDto.getFlowId());

        // 修改流程部署记录的状态为失败和结束
        publishDeployDto.setStage(PublishDeployConstant.PUBLISH_DEPLOYSTAGE_END);
        publishDeployDto.setStatus(PublishDeployConstant.PUBLISH_DEPLOYSTATUS_FAILED);
        update(publishDeployMapper.toEntity(publishDeployDto));
    }

    private void processSuccess(PublishDeployDto publishDeployDto, List<DeployRec> flowDeployRecList) {
        Long flowId = publishDeployDto.getFlowId();

        Long successCount = flowDeployRecList.stream()
                .filter(deployR -> deploySuccess(deployR)).count();
        Long totalCount = Long.valueOf(flowDeployRecList.size());
        if(successCount.equals(totalCount)) {
            // 全部成功删除备份的分支
            publishSheetFlowService.deleteBranchByFlow(flowId);

            // 修改流程部署记录的状态为成功和结束
            publishDeployDto.setStage(PublishDeployConstant.PUBLISH_DEPLOYSTAGE_END);
            publishDeployDto.setStatus(PublishDeployConstant.PUBLISH_DEPLOYSTATUS_SUCCESS);
            update(publishDeployMapper.toEntity(publishDeployDto));
        }
    }

    @Override
    public void process(DeployRec deployRec) {
        Long publishDeployId = deployRec.getPublishDeployId();
        if(publishDeployId != null) {
            Integer deployStatus = deployRec.getDeployStatus();

            PublishDeployDto publishDeployDto = findById(publishDeployId.intValue());

            // 获取相关联的部署流程
            List<DeployRec> flowDeployRecList = queryDeployRecList(publishDeployId);

            // 失败则回退流程关联的所有分支
            if(DeployConstant.DEPLOY_STATUS_FAILED.equals(deployStatus) || DeployConstant.DEPLOY_STATUS_CANCEL.equals(deployStatus)) {
                processFailed(publishDeployDto, flowDeployRecList);
                return;
            }

            // 成功则判断同流程的分支是否也成功
            if(deploySuccess(deployRec)) {
                processSuccess(publishDeployDto, flowDeployRecList);
            }
        }
    }
}
